package com.geek.gkfood.framework.redis.listener;

import com.geek.gkfood.framework.trace.TraceContext;
import com.geek.gkfood.framework.trace.model.TraceInfo;
import com.geek.gkfood.util.json.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * <h1>Redis 消息接收的抽象监听器。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2023/9/24 09:37
 */
@Slf4j
public abstract class AbstractRedisListener<T> extends MessageListenerAdapter {

    private static final String ENTRY_POINT_NAME = AbstractRedisListener.class.getSimpleName();

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * <h2>处理redis消息。</h2>
     *
     * @param msgObject
     */
    protected abstract void doRecvMsg(T msgObject);

    @Override
    public void onMessage(Message message, byte[] pattern) {
        TraceInfo mainTrace = null;
        TraceInfo subTrace = null;
        T msgObject = null;
        try {
            mainTrace = TraceContext.getOrCreate();
            subTrace = TraceContext.getCurrentTrace();
            TraceContext.start(mainTrace, subTrace, ENTRY_POINT_NAME);

            // 基本的反序列化操作
            msgObject = deserializeToObject(message);
            if (msgObject == null) {
                return;
            }

            // 处理redis消息
            doRecvMsg(msgObject);
        } catch (Exception e) {
            String errorMsg = String.format("recv redis message, unknow exception occur, msgObject=%s", msgObject);
            log.error(errorMsg, e);
        } finally {
            TraceContext.end(subTrace, ENTRY_POINT_NAME);
        }
    }

    @Override
    protected void handleListenerException(Throwable e) {
        log.error("recv redis message, unknow exception occur.", e);
    }

    private T deserializeToObject(Message message) {
        T msgObject;
        String deserialize = null;
        try {
            RedisSerializer<String> valueSerializer = redisTemplate.getStringSerializer();
            deserialize = valueSerializer.deserialize(message.getBody());
            if (StringUtils.isBlank(deserialize)) {
                log.error("recv redis message is null.");
                return null;
            }

            // 获取当前泛型类，即AbstractMessageListenerAdapter<T>
            ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();

            // 当前泛型类只有一个参数T, 此type为Class<T>
            Type msgType = parameterizedType.getActualTypeArguments()[0];

            // 将 recvMsg 解析为 T 类型的数据，如果为 String 类型的话，就不用转了，直接使用
            if (String.class.equals(msgType)) {
                msgObject = (T) deserialize;
            } else {
                msgObject = JacksonUtils.fromJson(deserialize, msgType);
            }

            if (msgObject == null) {
                log.error("recv redis message, entity is null.");
                return null;
            }

            log.info("recv redis message={}", deserialize);
            return msgObject;
        } catch (Exception e) {
            String errMsg = String.format("Redis message format error, msg=%s", deserialize);
            log.warn(errMsg, e);
            return null;
        }
    }
}